// AFI Summit 2026 - Integracion ePayco + Gravity Forms v4 // Endpoint correcto: https://secure.payco.co/checkout.php define( 'AFIDRO_PUBLIC_KEY', 'd4dd7d79c823f7b894d8c943f43e29e5' ); define( 'AFIDRO_CLIENTE_ID', '1581713' ); define( 'AFIDRO_TEST', false ); define( 'AFIDRO_FORM_ID', 3 ); define( 'AFIDRO_PRECIO', 500000 ); define( 'AFIDRO_IVA_PCT', 0.19 ); // IDs de campos Gravity Forms define( 'F_NOMBRE', 1 ); define( 'F_EMPRESA', 3 ); define( 'F_CARGO', 4 ); define( 'F_EMAIL', 5 ); define( 'F_TELEFONO', 6 ); define( 'F_CANTIDAD', 13 ); define( 'F_NOM_FAC', 15 ); define( 'F_NIT', 16 ); define( 'F_DIR', 17 ); define( 'F_CIUDAD', 18 ); define( 'F_EMAIL_FAC', 19 ); define( 'F_CEDULA', 20 ); // Validar cédula duplicada en Gravity Forms add_filter( 'gform_validation_3', 'afidro_validar_cedula_duplicada' ); function afidro_validar_cedula_duplicada( $validation_result ) { $form = $validation_result['form']; $cedula = rgpost( 'input_20' ); if ( empty( $cedula ) ) return $validation_result; $entries = GFAPI::get_entries( 3, array( 'status' => 'active', 'payment_status' => 'Paid', ) ); foreach ( $entries as $entry ) { if ( trim( $entry['20'] ?? '' ) === trim( $cedula ) ) { $validation_result['is_valid'] = false; foreach ( $form['fields'] as &$field ) { if ( $field->id == 20 ) { $field->failed_validation = true; $field->validation_message = 'Esta cédula ya tiene una inscripción confirmada en el AFI Summit 2026.'; } } break; } } $validation_result['form'] = $form; return $validation_result; } // ------------------------------------------------------- // CONFIRMACION: formulario POST hacia ePayco Standard Checkout // ------------------------------------------------------- add_filter( 'gform_confirmation', 'afidro_redirigir_epayco', 10, 4 ); function afidro_redirigir_epayco( $confirmation, $form, $entry, $ajax ) { if ( (int) $form['id'] !== AFIDRO_FORM_ID ) { return $confirmation; } $nombre = trim( rgar( $entry, F_NOMBRE . '.3' ) . ' ' . rgar( $entry, F_NOMBRE . '.6' ) ); if ( empty( $nombre ) ) { $nombre = rgar( $entry, F_NOMBRE ); } $email = rgar( $entry, F_EMAIL ); $telefono = rgar( $entry, F_TELEFONO ); $ciudad = rgar( $entry, F_CIUDAD ); $cantidad = (int) rgar( $entry, F_CANTIDAD ); if ( $cantidad < 1 ) { $cantidad = 1; } if ( $cantidad > 20 ) { $cantidad = 20; } $base = AFIDRO_PRECIO * $cantidad; // Aplicar código promocional si existe $codigo_promo = strtoupper( trim( rgar( $entry, '14' ) ) ); if ( ! empty( $codigo_promo ) && function_exists( 'afidro_get_codigos_promocionales' ) ) { $codigos = afidro_get_codigos_promocionales(); if ( isset( $codigos[ $codigo_promo ] ) ) { $usado = get_option( 'afidro_codigo_usado_' . md5( $codigo_promo ), false ); if ( ! $usado ) { // Protección contra race condition con lock temporal $lock_key = 'afidro_codigo_lock_' . md5( $codigo_promo ); if ( get_transient( $lock_key ) ) { return '

El código está siendo procesado. Por favor intenta de nuevo en unos segundos.

'; } set_transient( $lock_key, 1, 30 ); $descuento_pct = $codigos[ $codigo_promo ]; // Bloquear más de 1 entrada para códigos INV (100%) if ( $descuento_pct === 100 && $cantidad > 1 ) { return '

Los códigos de cortesía son válidos para 1 entrada únicamente. Por favor regresa y selecciona 1 entrada.

'; } $base = round( $base * ( 1 - $descuento_pct / 100 ) ); } } } // Aplicar descuento por volumen si no hay código promocional if ( empty( $codigo_promo ) && function_exists( 'afidro_descuento_por_volumen' ) ) { $descuento_vol = afidro_descuento_por_volumen( $cantidad ); if ( $descuento_vol > 0 ) { $base = round( $base * ( 1 - $descuento_vol / 100 ) ); } } $iva = round( $base * AFIDRO_IVA_PCT ); $total = $base + $iva; // Si el total es 0 (código 100%), confirmar sin pasar por ePayco if ( (int) $total === 0 ) { GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Paid' ); GFAPI::update_entry_property( $entry['id'], 'payment_amount', 0 ); GFAPI::update_entry_property( $entry['id'], 'payment_date', current_time( 'mysql' ) ); gform_update_meta( $entry['id'], 'epayco_transaction_id', 'INV-' . $entry['id'] ); gform_update_meta( $entry['id'], 'epayco_estado', 'Aceptada' ); gform_update_meta( $entry['id'], 'epayco_cantidad', $cantidad ); $codigo_promo_inv = strtoupper( trim( rgar( $entry, '14' ) ) ); update_option( 'afidro_codigo_usado_' . md5( $codigo_promo_inv ), array( 'entry_id' => $entry['id'], 'fecha' => current_time( 'mysql' ), 'email' => rgar( $entry, '5' ), ) ); afidro_email_inscrito( $entry['id'], 'INV-' . $entry['id'], 0 ); afidro_email_contabilidad( $entry['id'], 'INV-' . $entry['id'], 0, 'Aceptada' ); $url = home_url( '/afisummit/confirmacion-pago/' ); return ''; } $concepto = $cantidad === 1 ? 'Inscripcion individual AFI Summit 2026' : 'Inscripcion grupal AFI Summit 2026 x' . $cantidad; $ref = 'AFI2026-' . $entry['id'] . '-' . time(); gform_update_meta( $entry['id'], 'epayco_ref', $ref ); gform_update_meta( $entry['id'], 'epayco_cantidad', $cantidad ); gform_update_meta( $entry['id'], 'epayco_total', $total ); // Firma SHA256 con parametros ePayco Standard $firma_string = AFIDRO_CLIENTE_ID . '^' . AFIDRO_P_KEY . '^' . $ref . '^' . number_format( $total, 2, '.', '' ) . '^' . 'COP'; $firma = md5( $firma_string ); $params = array( 'p_cust_id_cliente' => AFIDRO_CLIENTE_ID, 'p_key' => AFIDRO_P_KEY, 'p_id_invoice' => $ref, 'p_description' => $concepto, 'p_currency_code' => 'COP', 'p_amount' => number_format( $total, 2, '.', '' ), 'p_tax' => number_format( $iva, 2, '.', '' ), 'p_amount_base' => number_format( $base, 2, '.', '' ), 'p_test_request' => AFIDRO_TEST ? 'TRUE' : 'FALSE', 'p_url_response' => home_url( '/afisummit/confirmacion-pago/' ), 'p_url_confirmation' => home_url( '/wp-json/afidro/v1/epayco-webhook/' ), 'p_signature' => $firma, 'p_billing_name' => rgar( $entry, F_NOM_FAC ), 'p_billing_address' => rgar( $entry, F_DIR ), 'p_billing_town' => $ciudad, 'p_billing_country' => 'CO', 'p_billing_email' => rgar( $entry, F_EMAIL_FAC ), 'p_billing_document' => rgar( $entry, F_NIT ), 'p_billing_phone' => $telefono, 'p_extra1' => $nombre, 'p_extra2' => $email, 'p_extra3' => $cantidad, ); $html = '
'; $html .= '
'; $html .= '

Redirigiendo al portal de pagos...

'; $html .= '

Por favor espera un momento.

'; $html .= '
'; $html .= '
'; foreach ( $params as $key => $value ) { $html .= ''; } $html .= '
'; $html .= ''; return $html; } // ------------------------------------------------------- // WEBHOOK: recibe confirmacion de pago de ePayco // ------------------------------------------------------- add_action( 'rest_api_init', 'afidro_registrar_webhook' ); function afidro_registrar_webhook() { register_rest_route( 'afidro/v1', '/epayco-webhook/', array( 'methods' => 'POST', 'callback' => 'afidro_procesar_webhook', 'permission_callback' => '__return_true', ) ); } function afidro_procesar_webhook( WP_REST_Request $request ) { $data = $request->get_params(); $cust_id = sanitize_text_field( $data['x_cust_id_cliente'] ?? '' ); $ref_payco = sanitize_text_field( $data['x_ref_payco'] ?? '' ); $transaccion = sanitize_text_field( $data['x_transaction_id'] ?? '' ); $monto = sanitize_text_field( $data['x_amount'] ?? '' ); $moneda = sanitize_text_field( $data['x_currency_code'] ?? '' ); $firma = sanitize_text_field( $data['x_signature'] ?? '' ); $estado = sanitize_text_field( $data['x_transaction_state'] ?? '' ); $ref_factura = sanitize_text_field( $data['x_id_invoice'] ?? '' ); // Rate limiting: máximo 20 llamadas por IP por minuto $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; $rk = 'afidro_webhook_rl_' . md5( $ip ); $hits = (int) get_transient( $rk ); if ( $hits > 20 ) { return new WP_REST_Response( array( 'status' => 'throttled' ), 429 ); } set_transient( $rk, $hits + 1, 60 ); // Verificar que el cliente ID corresponde a AFIDRO if ( $cust_id !== AFIDRO_CLIENTE_ID ) { return new WP_REST_Response( array( 'status' => 'error' ), 401 ); } $firma_local = hash( 'sha256', $cust_id . '^' . AFIDRO_PRIVATE_KEY . '^' . $ref_payco . '^' . $transaccion . '^' . $monto . '^' . $moneda ); if ( $firma_local !== $firma ) { return new WP_REST_Response( array( 'status' => 'error' ), 401 ); } $entry_id = afidro_buscar_entrada( $ref_factura ); $ya_pagado = gform_get_meta( $entry_id, 'epayco_estado' ); if ( $entry_id && $estado === 'Aceptada' && $ya_pagado !== 'Aceptada' ) { GFAPI::update_entry_property( $entry_id, 'payment_status', 'Paid' ); GFAPI::update_entry_property( $entry_id, 'payment_amount', $monto ); GFAPI::update_entry_property( $entry_id, 'transaction_id', $transaccion ); GFAPI::update_entry_property( $entry_id, 'payment_date', current_time( 'mysql' ) ); gform_update_meta( $entry_id, 'epayco_transaction_id', $transaccion ); gform_update_meta( $entry_id, 'epayco_estado', $estado ); // Marcar código promocional como usado $codigo_usado = strtoupper( trim( rgar( GFAPI::get_entry( $entry_id ), '14' ) ) ); if ( ! empty( $codigo_usado ) && function_exists( 'afidro_get_codigos_promocionales' ) ) { $codigos = afidro_get_codigos_promocionales(); if ( isset( $codigos[ $codigo_usado ] ) ) { update_option( 'afidro_codigo_usado_' . md5( $codigo_usado ), array( 'entry_id' => $entry_id, 'fecha' => current_time( 'mysql' ), 'email' => rgar( GFAPI::get_entry( $entry_id ), '5' ), ) ); } } afidro_email_inscrito( $entry_id, $transaccion, $monto ); afidro_email_contabilidad( $entry_id, $transaccion, $monto, $estado ); } return new WP_REST_Response( array( 'status' => 'ok' ), 200 ); } function afidro_buscar_entrada( $ref ) { global $wpdb; $id = $wpdb->get_var( $wpdb->prepare( "SELECT entry_id FROM {$wpdb->prefix}gf_entry_meta WHERE meta_key = 'epayco_ref' AND meta_value = %s LIMIT 1", $ref ) ); return $id ? (int) $id : null; } // ------------------------------------------------------- // EMAIL AL INSCRITO // ------------------------------------------------------- function afidro_email_inscrito( $entry_id, $transaccion, $monto ) { $entry = GFAPI::get_entry( $entry_id ); if ( ! $entry ) return; $nombre = trim( rgar( $entry, F_NOMBRE . '.3' ) . ' ' . rgar( $entry, F_NOMBRE . '.6' ) ) ?: rgar( $entry, F_NOMBRE ); $email = rgar( $entry, F_EMAIL ); $cantidad = (int) gform_get_meta( $entry_id, 'epayco_cantidad' ); $valor = '$' . number_format( (float) $monto, 0, ',', '.' ); $asunto = 'Inscripcion confirmada - AFI Summit 2026'; $cuerpo = ''; $cuerpo .= '
'; $cuerpo .= '

Inscripcion confirmada

'; $cuerpo .= '

Hola ' . esc_html( $nombre ) . ',

'; $cuerpo .= '

Tu inscripcion al AFI Summit 2026 ha sido confirmada.

'; $cuerpo .= '
'; $cuerpo .= '

📅 Cóctel de apertura: 16 de septiembre de 2026 · 5:30 PM – 8:30 PM
Incluye ceremonia de Reconocimiento al Periodismo en Salud

'; $cuerpo .= '

📅 Jornada principal: 17 de septiembre de 2026 · 8:00 AM – 5:00 PM

'; $cuerpo .= '

📍 Lugar: Hall 74, Calle 74 #14-25, Bogotá

'; $cuerpo .= '

Entradas: ' . $cantidad . '

'; $cuerpo .= '

Valor pagado: ' . $valor . ' COP

'; $cuerpo .= '

Referencia: ' . esc_html( $transaccion ) . '

'; $cuerpo .= '
'; if ( $cantidad > 1 ) { $token = gform_get_meta( $entry_id, 'registro_token' ); if ( empty( $token ) ) { $token = wp_generate_password( 12, false ); gform_update_meta( $entry_id, 'registro_token', $token ); gform_update_meta( $entry_id, 'registro_token_used', 0 ); } $adicionales = $cantidad - 1; $link = home_url( '/afisummit/registro-asistentes/?token=' . $token . '&ref=' . $transaccion . '&t=' . time() ); $cuerpo .= '
'; $cuerpo .= '

Importante: Debes registrar los datos de ' . $adicionales . ' asistente(s) adicional(es).

'; $cuerpo .= '

Registrar asistentes

'; $cuerpo .= '

Enlace válido para un solo uso. Referencia: ' . esc_html( $transaccion ) . '

'; $cuerpo .= '
'; } $cuerpo .= '

Consultas: eventos@afidro.org

'; $cuerpo .= '
'; wp_mail( $email, $asunto, $cuerpo, array( 'Content-Type: text/html; charset=UTF-8' ) ); } // ------------------------------------------------------- // EMAIL A CONTABILIDAD / EVENTOS / SOPORTE // ------------------------------------------------------- function afidro_email_contabilidad( $entry_id, $transaccion, $monto, $estado ) { $entry = GFAPI::get_entry( $entry_id ); if ( ! $entry ) return; $nombre = trim( rgar( $entry, F_NOMBRE . '.3' ) . ' ' . rgar( $entry, F_NOMBRE . '.6' ) ) ?: rgar( $entry, F_NOMBRE ); $empresa = rgar( $entry, F_EMPRESA ); $cargo = rgar( $entry, F_CARGO ); $email = rgar( $entry, F_EMAIL ); $telefono = rgar( $entry, F_TELEFONO ); $cantidad = (int) gform_get_meta( $entry_id, 'epayco_cantidad' ); $nom_fac = rgar( $entry, F_NOM_FAC ); $nit = rgar( $entry, F_NIT ); $dir = rgar( $entry, F_DIR ); $ciudad = rgar( $entry, F_CIUDAD ); $em_fac = rgar( $entry, F_EMAIL_FAC ); $monto_num = (float) $monto; $base = round( $monto_num / 1.19 ); $iva = $monto_num - $base; $total_fmt = '$' . number_format( $monto_num, 0, ',', '.' ); $base_fmt = '$' . number_format( $base, 0, ',', '.' ); $iva_fmt = '$' . number_format( $iva, 0, ',', '.' ); $concepto = $cantidad === 1 ? 'Inscripcion individual AFI Summit 2026' : 'Inscripcion grupal AFI Summit 2026 x' . $cantidad; $asunto = 'Nueva inscripcion pagada - AFI Summit 2026 | ' . $nombre; $c = ''; $c .= '
'; $c .= '

Nueva inscripcion - AFI Summit 2026

'; $c .= '

Datos para facturacion

'; $c .= ''; $c .= ''; $c .= ''; $c .= ''; $c .= ''; $c .= ''; $c .= ''; $c .= '
Nombre / Razon social' . esc_html( $nom_fac ) . '
NIT / Cedula' . esc_html( $nit ) . '
Direccion' . esc_html( $dir ) . '
Ciudad' . esc_html( $ciudad ) . '
Email factura' . esc_html( $em_fac ) . '
Concepto' . esc_html( $concepto ) . '
'; $c .= '

Datos de la transaccion

'; $c .= ''; $c .= ''; $c .= ''; $c .= ''; $c .= ''; $c .= ''; $c .= ''; $c .= '
Referencia ePayco' . esc_html( $transaccion ) . '
Estado' . esc_html( $estado ) . '
Entradas' . $cantidad . '
Valor base sin IVA' . $base_fmt . ' COP
IVA 19%' . $iva_fmt . ' COP
TOTAL PAGADO' . $total_fmt . ' COP
'; $c .= '

Datos del inscrito

'; $c .= ''; $c .= ''; $c .= ''; $c .= ''; $c .= ''; $c .= ''; $c .= '
Nombre' . esc_html( $nombre ) . '
Empresa' . esc_html( $empresa ) . '
Cargo' . esc_html( $cargo ) . '
Email' . esc_html( $email ) . '
Telefono' . esc_html( $telefono ) . '
'; $c .= '
'; $headers = array( 'Content-Type: text/html; charset=UTF-8' ); $destinatarios = array( 'eventos@afidro.org', 'soporte@afidro.org', 'facturacion@afidro.org', ); foreach ( $destinatarios as $dest ) { wp_mail( $dest, $asunto, $c, $headers ); } }